import { cloneDeep, groupBy, mapValues, uniqBy } from "lodash-es";
import type {
  BaseImportDeclaration,
  ImportSpecifier,
  MixedImportDeclaration,
  NamespaceImportDeclaration,
} from "@/types";

const IdentifierReg = /^[_$a-zA-Z][_$a-zA-Z0-9]*$/;
export const isIdentifier = (exp: string) => IdentifierReg.test(exp);

const StringLiteralReg = /^('|").*\1$/;
export const isStringLiteral = (exp: string) => StringLiteralReg.test(exp);

export const isBooleanLiteral = (exp: string) =>
  exp === "true" || exp === "false";

const SimpleImportDeclarationReg = {
  /** import A, { a as b, c } from 'A' */
  Mixed:
    /^\s*import\s*(?<default>[^\s{}]+\s*,?)?\s*(?<attachments>{\s*.*\s*})?\s*from\s*(?<delimiter>['"])(?<source>.+)\k<delimiter>\s*;?\s*$/,
  /** import * as A from 'A' */
  Namespace:
    /^\s*import\s*\*\s*as\s+(?<namespace>\S+)\s*from\s*(?<delimiter>['"])(?<source>.+)\k<delimiter>\s*;?\s*$/,
};

function throwImportSyntaxError(exp: string): never {
  throw new Error(`非法的import语句: ${exp}`);
}

function throwVariableSyntaxError(variable: string, context: string): never {
  throw new Error(`非法的变量名: ${variable}，上下文：${context}`);
}

function parseMixedImportDeclaration(
  declaration: string,
  context: {
    default: string | undefined;
    attachments: string | undefined;
    source: string;
  }
): MixedImportDeclaration {
  let { default: _default, attachments, source } = context;

  if (!_default && !attachments) {
    throwImportSyntaxError(declaration);
  }

  if (_default) {
    // import A, from "A"
    if (!attachments && _default.endsWith(",")) {
      throwImportSyntaxError(declaration);
    }

    // import A {} from "A"
    if (attachments && !_default.endsWith(",")) {
      throwImportSyntaxError(declaration);
    }

    if (_default.endsWith(",")) {
      _default = _default.slice(0, _default.length - 1);
    }
    _default = _default.trim();

    if (!isIdentifier(_default)) {
      throwVariableSyntaxError(_default, declaration);
    }
  }

  let importSpecifiers: ImportSpecifier[] = [];
  if (attachments) {
    // { a, b } -> a, b
    attachments = attachments.slice(1, attachments.length - 1).trim();
    if (attachments.startsWith(",")) {
      throwImportSyntaxError(declaration);
    }
    const segements = attachments.split(",").filter((e) => e);
    importSpecifiers = segements.map<ImportSpecifier>((segement) => {
      const [imported, local] = segement.split("as").map((a) => a.trim());
      if (!isIdentifier(imported)) {
        throwVariableSyntaxError(imported, declaration);
      }
      if (local && !isIdentifier(local)) {
        throwVariableSyntaxError(local, declaration);
      }
      return {
        type: "ImportSpecifier",
        imported,
        local: local || imported,
      };
    });
  }
  const ret: MixedImportDeclaration = {
    source,
    specifiers: [],
  };
  if (_default) {
    ret.specifiers[0] = {
      type: "ImportDefaultSpecifier",
      local: _default,
    };
  }
  ret.specifiers.push(...importSpecifiers);
  return ret;
}

function parseNamespaceImportDeclaration(
  declaration: string,
  ctx: {
    namespace: string;
    source: string;
  }
): NamespaceImportDeclaration {
  const { namespace, source } = ctx;
  if (!isIdentifier(namespace)) {
    throwVariableSyntaxError(namespace, declaration);
  }
  return {
    source,
    specifiers: [
      {
        type: "ImportNamespaceSpecifier",
        local: namespace,
      },
    ],
  } as NamespaceImportDeclaration;
}

export function parseImportDeclaration(declaration: string) {
  const { Mixed, Namespace } = SimpleImportDeclarationReg;
  const matches1 = declaration.match(Mixed);
  if (matches1) {
    return parseMixedImportDeclaration(
      declaration,
      mapValues(matches1.groups!, (a = "") => a.trim()) as any
    );
  } else {
    const matches2 = declaration.match(Namespace);
    if (matches2) {
      return parseNamespaceImportDeclaration(
        declaration,
        mapValues(matches2.groups!, (a = "") => a.trim()) as any
      );
    } else {
      throwImportSyntaxError(declaration);
    }
  }
}

export function hasImportDefaultSpecifier(declaration: BaseImportDeclaration) {
  return declaration.specifiers[0]?.type === "ImportDefaultSpecifier";
}

function justOwnImportSpecifier(declaration: BaseImportDeclaration) {
  return declaration.specifiers.every((s) => s.type === "ImportSpecifier");
}

export function isNamespaceDeclaration(
  declaration: BaseImportDeclaration
): declaration is NamespaceImportDeclaration {
  return declaration.specifiers[0]?.type === "ImportNamespaceSpecifier";
}

function uniqueImportSpecifiers(declaration: MixedImportDeclaration) {
  const uniqueSymbol = Symbol();
  // @ts-expect-error
  declaration.specifiers = uniqBy(declaration.specifiers, (a) =>
    a.type === "ImportDefaultSpecifier" ? uniqueSymbol : a.local + a.imported
  );
}

export function mergeImportDeclarations(
  declarations: BaseImportDeclaration[]
): BaseImportDeclaration[] {
  declarations = cloneDeep(declarations);
  // 根据source进行分组
  const groupedDeclarations = groupBy(declarations, (d) => d.source);

  const mergedImportDeclarations = Object.keys(groupedDeclarations).map(
    (source) => {
      const declarations = groupedDeclarations[source];

      // 找到一个拥有默认导入的声明 作为插槽
      let index = declarations.findIndex(hasImportDefaultSpecifier);
      // 否则寻找import {} from 'A'
      index =
        index >= 0 ? index : declarations.findIndex(justOwnImportSpecifier);

      const mergedPlace = declarations[index];
      if (!mergedPlace) return declarations;

      const reservedIndexes: number[] = [];
      for (let i = 0; i < declarations.length; i++) {
        // 保留：
        // 1、默认导入声明 import A from 'A'
        // 2、命名空间导入声明 import * as A from 'A'
        // 3、插槽导入声明
        if (
          i === index ||
          hasImportDefaultSpecifier(declarations[i]) ||
          isNamespaceDeclaration(declarations[i])
        ) {
          reservedIndexes.push(i);
          continue;
        }

        // import { a, b, c } from "A"
        // 上面的 ImportSpecifiers 放至插槽中
        mergedPlace.specifiers.push(...declarations[i].specifiers);
      }

      uniqueImportSpecifiers(mergedPlace as MixedImportDeclaration);

      return reservedIndexes.map((i) => declarations[i]);
    }
  );

  return mergedImportDeclarations.flat();
}

export function getAllVariables(declaration: BaseImportDeclaration) {
  return declaration.specifiers.map((s) => s.local);
}

export function getFirstVariable(declaration: BaseImportDeclaration) {
  return getAllVariables(declaration)[0] || null;
}
